/* str.c : dgnsc string func
 * Copyright (C) 2009-2011 drangon <drangon.zhou@gmail.com>
 * 2011-09
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <dgnsc/str.h>

#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdarg.h>

struct dgn_str_st
{
	int cap;  // special, cap == 0, str not malloc, point to len ( must be 0 )
	int len;
	char * str;
};

dgn_str_t dgn_str_init( int capacity )
{
	struct dgn_str_st * s = (struct dgn_str_st *)malloc( sizeof(struct dgn_str_st) );
	s->len = 0;
	if( capacity > 0 ) {
		s->cap = capacity;
		s->str = (char *)malloc( s->cap );
		s->str[0] = '\0';
	}
	else {
		s->cap = 0;
		s->str = (char *)&s->len;
	}
	return (dgn_str_t)s;
}

dgn_str_t dgn_str_init_dup( const dgn_str_t str )
{
	const struct dgn_str_st * s2 = (const struct dgn_str_st *)str;

	struct dgn_str_st * s = (struct dgn_str_st *)dgn_str_init( s2->cap );
	if( s2->len > 0 )
		memcpy( s->str, s2->str, s2->len + 1 );
	return (dgn_str_t)s;
}

dgn_str_t dgn_str_init_c( const char * str, int len ) // len == -1 means total
{
	if( str == NULL || len == 0 )
		return dgn_str_init( 0 );
	if( len < 0 )
		len = strlen( str );
	struct dgn_str_st * s = (struct dgn_str_st *)dgn_str_init( len + 1 );
	memcpy( s->str, str, len );
	s->str[len] = '\0';
	s->len = len;
	return (dgn_str_t)s;
}

int dgn_str_reserve( dgn_str_t me, int capacity )
{
	struct dgn_str_st * s = (struct dgn_str_st *)me;
	if( s->cap == 0 && capacity <= 0 )
		return 0;
	if( s->cap < capacity ) {
		int cap = s->cap * 17 / 8; // 2.125
		if( cap < capacity )
			cap = capacity;
		char * p = (char *)malloc( cap );
		if( s->len > 0 )
			memcpy( p, s->str, s->len );
		p[s->len] = '\0';
		if( s->cap != 0 )
			free( s->str );
		s->str = p;
		s->cap = cap;
	}
	return s->cap;
}

int dgn_str_len( const dgn_str_t me )
{
	return ((struct dgn_str_st *)me)->len;
}

const char * dgn_str_c( const dgn_str_t me ) // never return NULL
{
	return ((struct dgn_str_st *)me)->str;
}

// auto enlarge
const char * dgn_str_assign( dgn_str_t me, const dgn_str_t str )
{
	struct dgn_str_st * s = (struct dgn_str_st *)me;
	const struct dgn_str_st * s2 = (const struct dgn_str_st *)str;
	if( s2->cap == 0 ) {
		if( s->cap == 0 )
			return s->str;
		s->cap = 0;
		s->len = 0;
		free( s->str );
		s->str = (char *)&s->len;
		return s->str;
	}

	s->len = 0;
	dgn_str_reserve( (dgn_str_t)s, s2->len + 1 );
	memcpy( s->str, s2->str, s2->len + 1 );
	s->len = s2->len;
	return s->str;
}

const char * dgn_str_assign_c( dgn_str_t me, const char * str, int len )
{
	struct dgn_str_st * s = (struct dgn_str_st *)me;

	if( str == NULL ) {
		if( s->cap == 0 )
			return s->str;
		s->cap = 0;
		s->len = 0;
		free( s->str );
		s->str = (char *)&s->len;
		return s->str;
	}

	if( len < 0 )
		len = strlen(str);
	if( len == 0 && s->cap == 0 )
		return s->str;

	s->len = 0;
	dgn_str_reserve( (dgn_str_t)s, len + 1 );
	if( len > 0 )
		memcpy( s->str, str, len );
	s->str[len] = '\0';
	s->len = len;
	return s->str;
}

const char * dgn_str_append( dgn_str_t me, const dgn_str_t str )
{
	struct dgn_str_st * s = (struct dgn_str_st *)me;
	const struct dgn_str_st * s2 = (const struct dgn_str_st *)str;
	if( s2->len == 0 )
		return s->str;
	dgn_str_reserve( (dgn_str_t)s, s->len + s2->len + 1 );
	memcpy( s->str + s->len, s2->str, s2->len + 1 );
	s->len += s2->len;
	return s->str;
}

const char * dgn_str_append_c( dgn_str_t me, const char * str, int len )
{
	struct dgn_str_st * s = (struct dgn_str_st *)me;
	if( str == NULL )
		return s->str;
	if( len < 0 )
		len = strlen(str);
	if( len == 0 )
		return s->str;
	dgn_str_reserve( (dgn_str_t)s, s->len + len + 1 );
	memcpy( s->str + s->len, str, len );
	s->str[s->len + len] = '\0';
	s->len += len;
	return s->str;
}

const char * dgn_str_assign_fmt( dgn_str_t me, const char * fmt, ... )
{
	if( fmt == NULL )
		return dgn_str_assign_c( me, NULL, 0 );

	struct dgn_str_st * s = (struct dgn_str_st *)me;
	int len = -1;
	if( s->cap > 0 ) {
		va_list ap;
		va_start( ap, fmt );
		len = vsnprintf( s->str, s->cap, fmt, ap );
		va_end( ap );
	}
	if( len < 0 ) {
		va_list ap;
		va_start( ap, fmt );
#ifdef _WIN32
		len = _vscprintf( fmt, ap );
#else
		len = vsnprintf( NULL, 0, fmt, ap );
#endif
		va_end( ap );
	}
	if( len >= s->cap ) {
		s->len = 0;
		dgn_str_reserve( (dgn_str_t)s, len + 1 );
		va_list ap;
		va_start( ap, fmt );
		len = vsnprintf( s->str, s->cap, fmt, ap );
		va_end( ap );
	}
	if( len >= 0 )
		s->len = len;
	else
		s->len = 0;
	s->str[s->len] = '\0';
	return s->str;
}

const char * dgn_str_append_fmt( dgn_str_t me, const char * fmt, ... )
{
	struct dgn_str_st * s = (struct dgn_str_st *)me;
	if( fmt == NULL )
		return s->str;

	int len = -1;
	if( s->cap > 0 ) {
		va_list ap;
		va_start( ap, fmt );
		len = vsnprintf( s->str + s->len, s->cap - s->len, fmt, ap );
		va_end( ap );
	}
	if( len < 0 ) {
		va_list ap;
		va_start( ap, fmt );
#ifdef _WIN32
		len = _vscprintf( fmt, ap );
#else
		len = vsnprintf( NULL, 0, fmt, ap );
#endif
		va_end( ap );
	}
	if( s->len + len >= s->cap ) {
		dgn_str_reserve( (dgn_str_t)s, s->len + len + 1 );
		va_list ap;
		va_start( ap, fmt );
		len = vsnprintf( s->str + s->len, s->cap - s->len, fmt, ap );
		va_end( ap );
	}
	if( len >= 0 )
		s->len += len;
	s->str[s->len] = '\0';
	return s->str;
}

int dgn_str_cmp( const dgn_str_t me, const dgn_str_t str )
{
	const struct dgn_str_st * s = (const struct dgn_str_st *)me;
	const struct dgn_str_st * s2 = (const struct dgn_str_st *)str;
	return strcmp( s->str, s2->str );
}

int dgn_str_cmp_c( const dgn_str_t me, const char * str, int len )
{
	const struct dgn_str_st * s = (const struct dgn_str_st *)me;
	if( len < 0 )
		return strcmp( s->str, str );
	return strncmp( s->str, str, len );
}

int dgn_str_casecmp( const dgn_str_t me, const dgn_str_t str )
{
	const struct dgn_str_st * s = (const struct dgn_str_st *)me;
	const struct dgn_str_st * s2 = (const struct dgn_str_st *)str;
#ifdef _WIN32
	return _stricmp( s->str, s2->str );
#else
	return strcasecmp( s->str, s2->str );
#endif
}

int dgn_str_casecmp_c( const dgn_str_t me, const char * str, int len )
{
	const struct dgn_str_st * s = (const struct dgn_str_st *)me;
#ifdef _WIN32
	if( len < 0 )
		return _stricmp( s->str, str );
	return _strnicmp( s->str, str, len );
#else
	if( len < 0 )
		return strcasecmp( s->str, str );
	return strncasecmp( s->str, str, len );
#endif
}

void dgn_str_fini( dgn_str_t me )
{
	struct dgn_str_st * s = (struct dgn_str_st *)me;
	if( s->cap != 0 )
		free( s->str );
	free( s );
	return;
}

